"""照片服务

处理照片标准化相关服务
"""

# 照片标准化
from copy import deepcopy
import math
from app.model import PhotoStandardModel, PhotoStandardWarning
from app.exception.base import BaseException
from app.model.basics_model import Rectangle
from app.utils.PhotoStandard import (
    PhotoStandard,
    fix_rotate_image,
    ErrorCode,
    error_msgs,
    WarnningCode,
    warning_msgs,
    PhotoStandardMode,
)
from app.utils.index import base642PIL, orientation, PIL2base64, bytes2PIL
from app.model.request_model import Photo
from app.lib import baidu, zuotang
from app.lib import yolo


class PhotoService:
    __photo: Photo
    __photo_standard_param: PhotoStandardModel
    __instance_standard: PhotoStandard

    def __init__(self, photo: Photo):
        self.__photo = photo

        self.__photo_standard_param = self.__call_third_party(self.__photo)
        if photo.config.mode == PhotoStandardMode.STRICT:
            self.__init_photo(1)
        else:
            self.__init_photo()

    def __init_photo(self, count=0):
        baidu_params = self.__photo_standard_param.options
        glasses_probability = (
            0
            if baidu_params.glasses.type == "none"
            else baidu_params.glasses.probability
        )
        mask_probability = (
            0 if baidu_params.mask.type == 0 else baidu_params.mask.probability
        )
        try:
            self.__instance_standard = PhotoStandard(
                image_base64=self.__photo_standard_param.image_base64,
                options={
                    "top": self.__photo_standard_param.top,
                    "chin_position": {
                        "x": baidu_params.landmark150.chin_2.x,
                        "y": baidu_params.landmark150.chin_2.y,
                    },
                    "cheek_left": {
                        "x": baidu_params.landmark150.cheek_left_4.x,
                        "y": baidu_params.landmark150.cheek_left_4.y,
                    },
                    "cheek_right": {
                        "x": baidu_params.landmark150.cheek_right_4.x,
                        "y": baidu_params.landmark150.cheek_right_4.y,
                    },
                    "nose_tip": {
                        "x": baidu_params.landmark150.nose_tip.x,
                        "y": baidu_params.landmark150.nose_tip.y,
                    },
                    "rotation": baidu_params.location.rotation,
                    "eye_status": baidu_params.eye_status,
                    "glasses_probability": glasses_probability,
                    "mask_probability": mask_probability,
                    "face_blur": baidu_params.quality.blur,
                    "face_spoofing": baidu_params.spoofing,
                    "face_quality": {
                        "left_eye": baidu_params.quality.occlusion.left_eye,
                        "right_eye": baidu_params.quality.occlusion.right_eye,
                        "nose": baidu_params.quality.occlusion.nose,
                        "mouth": baidu_params.quality.occlusion.mouth,
                        "left_cheek": baidu_params.quality.occlusion.left_cheek,
                        "right_cheek": baidu_params.quality.occlusion.right_cheek,
                        "chin_contour": baidu_params.quality.occlusion.chin_contour,
                    },
                    "face_location": Rectangle(
                        baidu_params.location.left,
                        baidu_params.location.top,
                        baidu_params.location.width,
                        baidu_params.location.height,
                    ),
                },
                config=deepcopy(self.__photo.config),
            )
        except BaseException as exc:
            if count > 0:
                raise exc
            if (
                exc.status_code == ErrorCode.LEFT_HANGING
                or exc.status_code == ErrorCode.RIGHT_HANGING
            ):
                # 将旋转灯0、以及中心点为照片中心点
                param = deepcopy(self.original_param)
                rotation = param.options.location.rotation

                param.image_base64 = self.original_bs64
                source_image = base642PIL(param.image_base64)

                param.top = self.original_top

                param.options.location.rotation = 0
                param.options.landmark150.nose_tip.x = source_image.width / 2

                self.__photo_standard_param = param
                # 重新来一遍
                self.__init_photo(1)

                if rotation > 2:
                    self.__instance_standard.warning.append(
                        PhotoStandardWarning(
                            code=WarnningCode.FACE_ROTATION,
                            msg=warning_msgs[WarnningCode.FACE_ROTATION].substitute(
                                direction="左边", rotation="2"
                            ),
                        )
                    )
                if rotation < -2:
                    self.__instance_standard.warning.append(
                        PhotoStandardWarning(
                            code=WarnningCode.FACE_ROTATION,
                            msg=warning_msgs[WarnningCode.FACE_ROTATION].substitute(
                                direction="右边", rotation="2"
                            ),
                        )
                    )
                if exc.status_code == ErrorCode.LEFT_HANGING:
                    self.__instance_standard.warning.append(
                        PhotoStandardWarning(
                            code=WarnningCode.LEFT_HANGING,
                            msg=warning_msgs[WarnningCode.LEFT_HANGING],
                        )
                    )
                if exc.status_code == ErrorCode.RIGHT_HANGING:
                    self.__instance_standard.warning.append(
                        PhotoStandardWarning(
                            code=WarnningCode.RIGHT_HANGING,
                            msg=warning_msgs[WarnningCode.RIGHT_HANGING],
                        )
                    )

                    # 调用第三方获取标准化参数
            else:
                raise exc

    def __call_third_party(self, photo: Photo) -> PhotoStandardModel:
        # 先加载图片进行大小检测和方向校正
        img_original = base642PIL(photo.base64)
        if img_original.mode == "RGBA":
            img_original = img_original.convert("RGB")
        img_fix = orientation(img_original)
        # todo: 照片大小尺寸拦截
        # 百度接口要求一：小于10M
        # 佐糖抠图接口要求：不超过15MB最大4096x4096
        # file_size = img_fix.tell()
        # if file_size > 1024*1024*10:
        #   raise Exception("图片大小超过10M")
        # width, height = img_fix.size
        # if width > 4096 or height > 4096:
        #   raise Exception("图片像素超过4096")

        bs64 = PIL2base64(img_fix)
        self.original_bs64 = bs64
        # 调用yolo接口
        top = yolo.get_photo_head_param(bs64)
        self.original_top = top
        # 调用百度接口
        param = baidu.get_photo_standard_param(bs64)
        self.original_param = deepcopy(param)
        # 进行一次基本校验
        config = self.__photo.config
        if config.check:
            cheek_left = param.options.landmark150.cheek_left_4
            cheek_right = param.options.landmark150.cheek_right_4
            a2 = abs(cheek_left.x - cheek_right.x) ** 2
            b2 = abs(cheek_left.y - cheek_right.y) ** 2
            face_width = int(math.sqrt(a2 + b2))
            face_rotation = param.options.location.rotation
            face_blur = param.options.quality.blur
            mask_probability = (
                0 if param.options.mask.type == 0 else param.options.mask.probability
            )
            code = None
            msg = None
            if top == 0:
                code = ErrorCode.HEAD_TOP
                msg = error_msgs[ErrorCode.HEAD_TOP]
            elif face_width < config.face_min_width:
                code = ErrorCode.FACE_MINIMUM_WIDTH
                msg = error_msgs[ErrorCode.FACE_MINIMUM_WIDTH].substitute(
                    face_min_width=config.face_min_width
                )
            elif face_rotation > config.face_ratio_max:
                code = ErrorCode.FACE_ROTATION
                msg = error_msgs[ErrorCode.FACE_ROTATION].substitute(
                    direction="左边", face_ratio_max=config.face_ratio_max
                )
            elif face_rotation < config.face_ratio_max * -1:
                code = ErrorCode.FACE_ROTATION
                msg = error_msgs[ErrorCode.FACE_ROTATION].substitute(
                    direction="右边", face_ratio_max=config.face_ratio_max
                )
            elif mask_probability > config.mask_probability:
                code = ErrorCode.FACE_MASK
                msg = error_msgs[ErrorCode.FACE_MASK]
            elif face_blur > config.face_blur:
                code = ErrorCode.FACE_BLUR
                msg = error_msgs[ErrorCode.FACE_BLUR]

            if code and msg:
                raise BaseException(status_code=code, detail=msg)

        # 判断如果有旋转先修复旋转
        rotation = param.options.location.rotation
        # print("实际旋转角度", rotation)
        # 自动模式才做旋转角度模糊处理
        if self.__photo.config.mode == PhotoStandardMode.AUTO:
            if rotation < -2:
                rotation = -2
            elif rotation > 2:
                rotation = 2
            else:
                rotation = 0
        # print("一次调用的旋转", rotation)
        if rotation != 0:
            nose_tip = param.options.landmark150.nose_tip
            rotation_center = (nose_tip.x, nose_tip.y)
            ret = fix_rotate_image(
                base642PIL(bs64), angle=rotation, pivot_point=rotation_center
            )
            bs64 = PIL2base64(ret)
            # 二次调用
            top = yolo.get_photo_head_param(bs64)
            param = baidu.get_photo_standard_param(bs64)
            # print("二次调用的旋转", param.options.location.rotation)
            # 强制等于0
            param.options.location.rotation = 0

        # param.options.landmark150.nose_tip.x = 1

        param.image_base64 = bs64
        param.top = top
        return param

    # 照片标准化
    def photo_standardized(self):
        # 获取标准化照片
        image_stardard, warnning = self.__instance_standard.get_standard_photo()
        if not image_stardard:
            raise Exception("stardard photo is empty")

        image_zoom = self.__instance_standard.resize(image_stardard)
        # image_stardard.save("image_result2.jpg", dpi=(300, 300), quality=100)
        # image_zoom.save("image_small2.jpg", dpi=(300, 300), quality=100)
        if not self.__photo.config.change_background_color:
            # 不更换背景
            # self.__instance_standard.check_image_pixels(image_zoom)
            bs64_seg = PIL2base64(image_zoom, quality=100)
        else:
            # 调用抠图换背景
            bin_seg = zuotang.body_seg(
                PIL2base64(image_zoom, quality=100),
                self.__photo.config.background_color,
            )
            if len(bin_seg) == 0:
                raise Exception("抠图图像内容为空")
            image_seg = bytes2PIL(bin_seg)
            # kt_img = base642PIL(bs64_seg)
            # kt_img.save("image_kt.jpg", dpi=(300, 300), quality=100)
            # 像素点检查底部是否悬空
            self.__instance_standard.check_image_pixels(image_seg)
            bs64_seg = PIL2base64(image_seg, quality=100)

        return bs64_seg, PIL2base64(image_stardard, quality=100), warnning

    def photo_standardized_only(self):
        # 获取标准化照片
        image_stardard, warnning = self.__instance_standard.get_standard_photo()
        if not image_stardard:
            raise Exception("stardard photo is empty")

        image_zoom = self.__instance_standard.resize(image_stardard)
        if not self.__photo.config.change_background_color:
            # 不更换背景
            bs64_seg = PIL2base64(image_zoom, quality=100)
        else:
            # 调用抠图换背景
            bin_seg = zuotang.body_seg(
                PIL2base64(image_zoom, quality=100),
                self.__photo.config.background_color,
            )
            if len(bin_seg) == 0:
                raise Exception("抠图图像内容为空")
            image_seg = bytes2PIL(bin_seg)
            # 像素点检查底部是否悬空
            self.__instance_standard.check_image_pixels(image_seg)
            bs64_seg = PIL2base64(image_seg, quality=100)

        return bs64_seg, warnning

    # 中间标记图
    def photo_mark(self):
        # 获取中间标记图
        return self.__instance_standard.get_intermediate_mark_photo()

    # 最终参数
    def final_parameter(self):
        return self.__instance_standard.config
